#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
// oslib
#include "os.h"
#include "sound.h"

#include "midiutils.h"
#include "midi.h"


void internalmapping(int voice, char *name) {

  int i, slot;
  char voicename[256];
  char *ptr;

  i = 0;
  while (name[i] > 0) {
    name[i] = toupper(name[i]);
    i++;
  }

  for (slot = 1; slot <= 32; slot++) {
    if (!xsoundinstallvoice_read_name(slot, &ptr)) {
      if (*ptr >= ' ') {
        i = 0;
        while (*ptr > 0)   voicename[i++] = toupper(*ptr++);
        voicename[i] = 0;
        if (strcmp(name, (char *)voicename) == NULL) {
          internalmap[voice] = slot;
          slot = 33;
        }
      }
    }
  }
}


// various midi functions
byte *readticks(byte *file, int *ticks) {

  int byte, n;

  byte = *file++;
  n = byte & 0x7f;
  if (byte & 0x80) {
    byte = *file++;
    n = (n << 7) | (byte & 0x7f);
    if (byte & 0x80) {
      byte = *file++;
      n = (n << 7) | (byte & 0x7f);
      if (byte & 0x80) {
        byte = *file++;
        n = (n << 7) | (byte & 0x7f);
      }
    }
  }

  *ticks = n;

  return file;
}

byte *readword(byte *file, int *ticks) {

  int word = 0;

  word = *file++;
  word = (word << 8) | *file++;
  word = (word << 8) | *file++;
  word = (word << 8) | *file++;
  *ticks = word;

  return file;
}

byte *readtriple(byte *file, int *ticks) {

  int word = 0;

  word = *file++;
  word = (word << 8) | *file++;
  word = (word << 8) | *file++;
  *ticks = word;

  return file;
}

byte *readshort(byte *file, int *ticks) {

  int word = 0;

  word = *file++;
  word = (word << 8) | *file++;
  *ticks = word;

  return file;
}


void init_soundsystem() {

  int i;

  xsound_configure(8, 0, 48, NULL, NULL, &CHANNELS, NULL, &RATE, NULL, NULL);
  xsound_enable(2, NULL);

  for (i = 1; i <= 8; i++) {
    xsound_attach_voice(i, 2, NULL, NULL);
    soundchannels[i].free = -1;
    soundchannels[i].internalvoice = -1;
  }

  xsound_qinit();                       // reset scheduler
  xsound_qtempo(0x1000, NULL);          // set tempo to 0.01 sec/beat
  xsound_qbeat(0x7f000000, NULL);       // 4 beats/bar

  STARTTIME = clock();

  POSITION = 0;                         // time
  NEXTEVENT = 0;                        // event count
}


void reset_soundsystem() {

  xsound_configure(CHANNELS, 0, RATE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}


int midi_countevents(byte *midi, byte *end) {

  int running, events, byte, status, channel, ticks, sysexlen, meta, metalen;

  running = 0;
  events = 0;
  do {
    midi = readticks(midi, &ticks);
    status = *midi++;
    if (status & 0x80)
      running = status;
    else {
      status = running;
      midi--;
    }
    channel = status & 0xf;
    switch (status & 0xf0) {
    case NOTEON:
      midi += 2;
      events++;
      break;
    case NOTEOFF:
    case KEYPRESSURE:
    case CONTROLCHANGE:
    case PITCHWHEEL:
      midi += 2;
      break;
    case PROGRAMCHANGE:
    case CHANNELPRESSURE:
      midi++;
      break;
    case SYSTEM:
      switch (channel) {
      case SYSTEM_META:
        meta = *midi++;
        metalen = *midi++;
        switch (meta) {
        case META_SETTEMPO:
          midi += 3;
          break;
        default:
          midi += metalen;
          break;
        } // switch meta
        break;
      case SYSTEM_EXCL1:
        sysexlen = *midi++;
        midi += sysexlen;
        break;
      case SYSTEM_EXCL2:
        midi++;
        do {
          byte = *midi++;
        } while (byte != SYSTEM_END);
        break;
      } // switch channel
    } // switch status
  } while ((int)midi < (int)end);

  return events;
}


void midi_unpacktrack(byte *midi, byte *end, struct midievent *output, int events) {

  int running, event, byte, status, channel, ticks, sysexlen, meta, metalen, tempo;
  int time, note, i, found, ctrl, value, voice;
  midievent *eventptr;

  running = 0;
  time = 0;
  event = 0;

  do {
    midi = readticks(midi, &ticks);
    time += ticks;
    status = *midi++;
    if (status & 0x80)
      running = status;
    else {
      status = running;
      midi--;
    }
    channel = status & 0xf;
    switch (status & 0xf0) {
    case NOTEOFF:
      note = *midi++;
      midi++;
      i = event - 1;
      found = -1;
      while ((i >= 0) && (found == -1)) {
        eventptr = output + i;
        if ((i >= 0) && (eventptr->duration == -1) &&
                        (eventptr->note == note)   &&
                        (eventptr->channel == channel))  found = i;
        i--;
      }
      if (found >= 0) {
        eventptr->duration = time - eventptr->time;
        note += eventptr->transpose - 128;
        if (note < 4) note = 4;
        if (note > 127) note = 127;
      }
      break;
    case NOTEON:
      note = *midi++;
      midi++;
      eventptr = output + event;
      eventptr->time = time;
      eventptr->channel = channel;
      if (channel == 9) {
        eventptr->voice = drummap[note];
        eventptr->note = 60 + drumtranspose[note];
        eventptr->transpose = 0;
      }
      else {
        eventptr->note = note;
        eventptr->voice = channelinfo[channel].internalvoice;
        eventptr->midiinstrument = channelinfo[channel].midiinstrument;
        eventptr->transpose = channelinfo[channel].transpose;
      }
      eventptr->duration = -1;
      eventptr->pan = channelinfo[channel].pan;
      eventptr->volume = channelinfo[channel].volume;
      event++;
      break;
    case CONTROLCHANGE:
      ctrl = *midi++;
      value = *midi++;
      switch (ctrl) {
      case CTRL_PAN:
        channelinfo[channel].pan = value & 0x7f;
        break;
      case CTRL_VOLUME:
        channelinfo[channel].volume = value & 0x7f;
        break;
      } // switch ctrl
      break;
    case KEYPRESSURE:
    case PITCHWHEEL:
      midi += 2;
      break;
    case PROGRAMCHANGE:
      voice = *midi++;
      channelinfo[channel].midiinstrument = voice;
      channelinfo[channel].internalvoice = voicemap[voice];
      channelinfo[channel].transpose = 128 + transposemap[voice];
      break;
    case CHANNELPRESSURE:
      midi++;
      break;
    case SYSTEM:
      switch (channel) {
      case SYSTEM_META:
        meta = *midi++;
        metalen = *midi++;
        switch (meta) {
        case META_SETTEMPO:
          midi = readtriple(midi, &tempo);
          if (TEMPO == 500000) TEMPO = tempo;
          break;
        default:
          midi += metalen;
          break;
        } // switch meta
        break;
      case SYSTEM_EXCL1:
        sysexlen = *midi++;
        midi += sysexlen;
        break;
      case SYSTEM_EXCL2:
        midi++;
        do {
          byte = *midi++;
        } while (byte != SYSTEM_END);
        break;
      } // switch channel
    } // switch status
  } while ((int)midi < (int)end);
}


void play_note(int time, int duration, int note, int pan, int volume, int voice) {

  int reg0, reg1, i, channel;

  note = ((note-12)*4096)/12;
  if (note < 0x100) note = 0x100;
  if (note > 0x7fff) note = 0x7fff;

  channel = 0;

  // check if there's a channel available with the right voice
  for (i = 1; i <= 8; i++) {
    if ((soundchannels[i].internalvoice == voice) &&
        (soundchannels[i].free < time+5*duration))     channel = i;
  }

  if (channel == 0) {
  // attempt to allocate a channel to a voice
    for (i = 1; i <= 8; i++) {
      if ((channel == 0) && (soundchannels[i].free < CURRENTBEAT)) {
        xsound_attach_voice(i, internalmap[voice], NULL, NULL);
        soundchannels[i].internalvoice = voice;
        channel = i;
      }
    }
  }

  if (channel == 0) {
  // find the channel that first becomes available
    int played, chn, best;
    chn = 0;
    best = -1;
    for (i = 1; i <= 8; i++) {
      if (soundchannels[i].internalvoice == voice) {
        played = CURRENTBEAT - soundchannels[i].start;
        if (played > best) {
          best = played;
          chn = i;
        }
      }
    }
    if (chn > 0) channel = chn;
  }

  if (channel == 0) {
  // find the channel that has been playing for the longest
    int played, longest;
    channel = 1;
    longest = -1;
    for (i = 1; i <= 8; i++) {
      played = CURRENTBEAT - soundchannels[i].start;
      if (played > longest) {
        longest = played;
        channel = i;
      }
    }
  }

  reg0 = (channel) | ((0x100 + volume) << 16);
  reg1 = (note) | (duration << 16);
  xsound_qschedule(time - CURRENTBEAT, 0, reg0, reg1, NULL);

  soundchannels[channel].internalvoice = voice;
  soundchannels[channel].free = time+5*duration;
  soundchannels[channel].start = time;
}
